Ensure fresh build commands populate build info
authorAlex Crichton <alex@alexcrichton.com>
Fri, 31 Oct 2014 23:20:13 +0000 (16:20 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Wed, 5 Nov 2014 19:37:34 +0000 (11:37 -0800)
Whenever a build command is fresh, we need to be sure to propagate its build
information upwards from the cache stored on disk.

src/cargo/ops/cargo_rustc/custom_build.rs
src/cargo/ops/cargo_rustc/fingerprint.rs
tests/test_cargo_compile_custom_build.rs

index 03a61b6b35585dad3e5f22a6e4dbf631be5612a9..5f161dc195de10c8efddf9ff0e18c59f8d95e655 100644 (file)
@@ -1,10 +1,11 @@
 use std::fmt;
-use std::io::{fs, BufReader, USER_RWX, File};
 use std::io::fs::PathExtensions;
+use std::io::{fs, USER_RWX, File};
+use std::str;
 
 use core::{Package, Target};
 use util::{CargoResult, CargoError, human};
-use util::{internal, ChainError};
+use util::{internal, ChainError, Require};
 
 use super::job::Work;
 use super::{fingerprint, process, KindHost, Context};
@@ -24,9 +25,10 @@ pub struct BuildOutput {
 /// Prepares a `Work` that executes the target as a custom build script.
 pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
                -> CargoResult<(Work, Work, Freshness)> {
-    let (script_output, build_output, old_build_output) = {
+    let (script_output, old_script_output, build_output, old_build_output) = {
         let layout = cx.layout(pkg, KindHost);
         (layout.build(pkg),
+         layout.proxy().old_build(pkg),
          layout.build_out(pkg),
          layout.proxy().old_build(pkg).join("out"))
     };
@@ -75,6 +77,8 @@ pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
     let lib_name = pkg.get_manifest().get_links().map(|s| s.to_string());
     let pkg_name = pkg.to_string();
     let native_libs = cx.native_libs.clone();
+    let all = (lib_name.clone(), pkg_name.clone(), native_libs.clone(),
+               script_output.clone());
 
     try!(fs::mkdir(&script_output, USER_RWX));
 
@@ -127,15 +131,17 @@ pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
         // This is also the location where we provide feedback into the build
         // state informing what variables were discovered via our script as
         // well.
-        let rdr = BufReader::new(output.output.as_slice());
-        let build_output = try!(BuildOutput::parse(rdr, pkg_name.as_slice()));
+        let output = try!(str::from_utf8(output.output.as_slice()).require(|| {
+            human("build script output was not valid utf-8")
+        }));
+        let build_output = try!(BuildOutput::parse(output, pkg_name.as_slice()));
         match lib_name {
             Some(name) => assert!(native_libs.lock().insert(name, build_output)),
             None => {}
         }
 
         try!(File::create(&script_output.join("output"))
-                  .write(output.output.as_slice()).map_err(|e| {
+                  .write_str(output).map_err(|e| {
             human(format!("failed to write output of custom build command: {}",
                           e))
         }));
@@ -150,10 +156,26 @@ pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
     // Note that the freshness calculation here is the build_cmd freshness, not
     // target specific freshness. This is because we don't actually know what
     // the inputs are to this command!
+    //
+    // Also note that a fresh build command needs to
     let (freshness, dirty, fresh) =
             try!(fingerprint::prepare_build_cmd(cx, pkg, Some(target)));
     let dirty = proc(tx: Sender<String>) { try!(work(tx.clone())); dirty(tx) };
     let fresh = proc(tx) {
+        let (lib_name, pkg_name, native_libs, script_output) = all;
+        let new_loc = script_output.join("output");
+        try!(fs::rename(&old_script_output.join("output"), &new_loc));
+        let mut f = try!(File::open(&new_loc).map_err(|e| {
+            human(format!("failed to read cached build command output: {}", e))
+        }));
+        let contents = try!(f.read_to_string());
+        let output = try!(BuildOutput::parse(contents.as_slice(),
+                                             pkg_name.as_slice()));
+        match lib_name {
+            Some(name) => assert!(native_libs.lock().insert(name, output)),
+            None => {}
+        }
+
         fresh(tx)
     };
 
@@ -163,18 +185,14 @@ pub fn prepare(pkg: &Package, target: &Target, cx: &mut Context)
 impl BuildOutput {
     // Parses the output of a script.
     // The `pkg_name` is used for error messages.
-    pub fn parse<B: Buffer>(mut input: B, pkg_name: &str) -> CargoResult<BuildOutput> {
+    pub fn parse(input: &str, pkg_name: &str) -> CargoResult<BuildOutput> {
         let mut library_paths = Vec::new();
         let mut library_links = Vec::new();
         let mut metadata = Vec::new();
         let whence = format!("build script of `{}`", pkg_name);
 
         for line in input.lines() {
-            // unwrapping the IoResult
-            let line = try!(line.map_err(|e| human(format!("Error while reading\
-                                                            custom build output: {}", e))));
-
-            let mut iter = line.as_slice().splitn(1, |c: char| c == ':');
+            let mut iter = line.splitn(1, |c: char| c == ':');
             if iter.next() != Some("cargo") {
                 // skip this line since it doesn't start with "cargo:"
                 continue;
index c6abcee591d971cec1590978eca557bc61a56c60..e69734ed88c0096f98700ffca37a96e0d8ff7fdf 100644 (file)
@@ -144,7 +144,7 @@ pub fn prepare_build_cmd(cx: &mut Context, pkg: &Package,
     // TODO: this should not explicitly pass KindTarget
     let kind = KindTarget;
 
-    if pkg.get_manifest().get_build().len() == 0 {
+    if pkg.get_manifest().get_build().len() == 0 && target.is_none() {
         return Ok((Fresh, proc(_) Ok(()), proc(_) Ok(())))
     }
     let (old, new) = dirs(cx, pkg, kind);
index 8df01574a89432bcd84943a2580d8570f2cac92b..4fbc82dcdaee7cf5beec3255e603d500f4315711 100644 (file)
@@ -1,5 +1,8 @@
-use support::{project, execs};
-use support::{COMPILING, RUNNING};
+use std::io::File;
+
+use support::{project, execs, cargo_dir};
+use support::{COMPILING, RUNNING, FRESH};
+use support::paths::PathExt;
 use hamcrest::{assert_that};
 
 fn setup() {
@@ -344,3 +347,88 @@ test!(links_passes_env_vars {
 ", compiling = COMPILING, running = RUNNING).as_slice()));
 })
 
+test!(only_rerun_build_script {
+    let p = project("foo")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "foo"
+            version = "0.5.0"
+            authors = []
+            build = "build.rs"
+        "#)
+        .file("src/lib.rs", "")
+        .file("build.rs", r#"
+            fn main() {}
+        "#);
+
+    assert_that(p.cargo_process("build").arg("-v"),
+                execs().with_status(0));
+    p.root().move_into_the_past().unwrap();
+
+    File::create(&p.root().join("some-new-file")).unwrap();
+
+    assert_that(p.process(cargo_dir().join("cargo")).arg("build").arg("-v"),
+                execs().with_status(0)
+                       .with_stdout(format!("\
+{compiling} foo v0.5.0 (file://[..])
+{running} `[..]build-script-build`
+{running} `rustc [..] --crate-name foo [..]`
+", compiling = COMPILING, running = RUNNING).as_slice()));
+})
+
+test!(rebuild_continues_to_pass_env_vars {
+    let a = project("a")
+        .file("Cargo.toml", r#"
+            [project]
+            name = "a"
+            version = "0.5.0"
+            authors = []
+            links = "foo"
+            build = "build.rs"
+        "#)
+        .file("src/lib.rs", "")
+        .file("build.rs", r#"
+            fn main() {
+                println!("cargo:foo=bar");
+                println!("cargo:bar=baz");
+            }
+        "#);
+    a.build();
+    a.root().move_into_the_past().unwrap();
+
+    let p = project("foo")
+        .file("Cargo.toml", format!(r#"
+            [project]
+            name = "foo"
+            version = "0.5.0"
+            authors = []
+            build = "build.rs"
+
+            [dependencies.a]
+            path = '{}'
+        "#, a.root().display()))
+        .file("src/lib.rs", "")
+        .file("build.rs", r#"
+            use std::os;
+            fn main() {
+                assert_eq!(os::getenv("DEP_FOO_FOO").unwrap().as_slice(), "bar");
+                assert_eq!(os::getenv("DEP_FOO_BAR").unwrap().as_slice(), "baz");
+            }
+        "#);
+
+    assert_that(p.cargo_process("build").arg("-v"),
+                execs().with_status(0));
+    p.root().move_into_the_past().unwrap();
+
+    File::create(&p.root().join("some-new-file")).unwrap();
+
+    assert_that(p.process(cargo_dir().join("cargo")).arg("build").arg("-v"),
+                execs().with_status(0)
+                       .with_stdout(format!("\
+{fresh} a v0.5.0 (file://[..])
+{compiling} foo v0.5.0 (file://[..])
+{running} `[..]build-script-build`
+{running} `rustc [..] --crate-name foo [..]`
+", compiling = COMPILING, running = RUNNING, fresh = FRESH).as_slice()));
+})
+